/*
 * Copyright (c) 2024 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

/**
 * @brief File containing API definitions for the
 * HAL Layer of the Wi-Fi driver.
 */

#include "queue.h"
#include "common/hal_structs_common.h"
#include "common/hal_common.h"
#include "common/hal_reg.h"
#include "common/hal_mem.h"
#ifndef NRF71_ON_IPC
#include "common/hal_interrupt.h"
#include "common/pal.h"
#endif /* !NRF71_ON_IPC */

#ifndef NRF71_ON_IPC
#ifdef NRF_WIFI_LOW_POWER
#ifdef NRF_WIFI_RPU_RECOVERY
static void did_rpu_had_sleep_opp(struct nrf_wifi_hal_dev_ctx *hal_dev_ctx)
{
	unsigned int deassert_time_diff_ms = nrf_wifi_osal_time_elapsed_ms(
		hal_dev_ctx->last_wakeup_now_deasserted_time_ms);

	if (deassert_time_diff_ms > NRF_WIFI_RPU_MIN_TIME_TO_ENTER_SLEEP_MS) {
		hal_dev_ctx->last_rpu_sleep_opp_time_ms =
			hal_dev_ctx->last_wakeup_now_deasserted_time_ms;
	}
}
#endif /* NRF_WIFI_RPU_RECOVERY */

enum nrf_wifi_status hal_rpu_ps_wake(struct nrf_wifi_hal_dev_ctx *hal_dev_ctx)
{
	unsigned int reg_val = 0;
	unsigned int rpu_ps_state_mask = 0;
	unsigned long start_time_us = 0;
	unsigned long idle_time_start_us = 0;
	unsigned long idle_time_us = 0;
	unsigned long elapsed_time_sec = 0;
	unsigned long elapsed_time_usec = 0;
	enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL;

	if (!hal_dev_ctx) {
		nrf_wifi_osal_log_err("%s: Invalid parameters",
				      __func__);
		return status;
	}


	/* If the FW is not yet booted up (e.g. during the FW load stage of Host FW load)
	 * then skip the RPU wake attempt since RPU sleep/wake kicks in only after FW boot
	 */
	if (!hal_dev_ctx->rpu_fw_booted)
		return NRF_WIFI_STATUS_SUCCESS;

	if (hal_dev_ctx->rpu_ps_state == RPU_PS_STATE_AWAKE) {
		status = NRF_WIFI_STATUS_SUCCESS;

		goto out;
	}

	nrf_wifi_bal_rpu_ps_wake(hal_dev_ctx->bal_dev_ctx);
#ifdef NRF_WIFI_RPU_RECOVERY
	hal_dev_ctx->is_wakeup_now_asserted = true;
	hal_dev_ctx->last_wakeup_now_asserted_time_ms =
		nrf_wifi_osal_time_get_curr_ms();
#endif /* NRF_WIFI_RPU_RECOVERY */
	start_time_us = nrf_wifi_osal_time_get_curr_us();

	rpu_ps_state_mask = ((1 << RPU_REG_BIT_PS_STATE) |
			     (1 << RPU_REG_BIT_READY_STATE));

	/* Add a delay to avoid a race condition in the RPU */
	/* TODO: Reduce to 200 us after sleep has been stabilized */
	nrf_wifi_osal_delay_us(1000);

	do {
		/* Poll the RPU PS state */
		reg_val = nrf_wifi_bal_rpu_ps_status(hal_dev_ctx->bal_dev_ctx);

		if ((reg_val & rpu_ps_state_mask) == rpu_ps_state_mask) {
			status = NRF_WIFI_STATUS_SUCCESS;
			break;
		}

		idle_time_start_us = nrf_wifi_osal_time_get_curr_us();

		do {
			idle_time_us = nrf_wifi_osal_time_elapsed_us(idle_time_start_us);
		} while ((idle_time_us / 1000) < RPU_PS_WAKE_INTERVAL_MS);

		elapsed_time_usec = nrf_wifi_osal_time_elapsed_us(start_time_us);
		elapsed_time_sec = (elapsed_time_usec / 1000000);
	} while (elapsed_time_sec < RPU_PS_WAKE_TIMEOUT_S);

	if (status != NRF_WIFI_STATUS_SUCCESS) {
		nrf_wifi_osal_log_err("%s: RPU is not ready for more than %d sec,"
				      "reg_val = 0x%X rpu_ps_state_mask = 0x%X",
				      __func__,
				      RPU_PS_WAKE_TIMEOUT_S,
				      reg_val,
				      rpu_ps_state_mask);
#ifdef NRF_WIFI_RPU_RECOVERY
		nrf_wifi_osal_tasklet_schedule(hal_dev_ctx->recovery_tasklet);
#endif /* NRF_WIFI_RPU_RECOVERY */
		goto out;
	}
	hal_dev_ctx->rpu_ps_state = RPU_PS_STATE_AWAKE;
#ifdef NRF_WIFI_RPU_RECOVERY
	did_rpu_had_sleep_opp(hal_dev_ctx);
#endif /* NRF_WIFI_RPU_RECOVERY */
#ifdef NRF_WIFI_RPU_RECOVERY_PS_STATE_DEBUG
	nrf_wifi_osal_log_info("%s: RPU PS state is AWAKE",
			       __func__);
#endif /* NRF_WIFI_RPU_RECOVERY_PS_STATE_DEBUG */

out:

	nrf_wifi_osal_timer_schedule(hal_dev_ctx->rpu_ps_timer,
		NRF70_RPU_PS_IDLE_TIMEOUT_MS);
	return status;
}


static void hal_rpu_ps_sleep(unsigned long data)
{
	struct nrf_wifi_hal_dev_ctx *hal_dev_ctx = NULL;
	unsigned long flags = 0;

	hal_dev_ctx = (struct nrf_wifi_hal_dev_ctx *)data;

	nrf_wifi_osal_spinlock_irq_take(hal_dev_ctx->rpu_ps_lock,
					&flags);

	nrf_wifi_bal_rpu_ps_sleep(hal_dev_ctx->bal_dev_ctx);
#ifdef NRF_WIFI_RPU_RECOVERY
	hal_dev_ctx->is_wakeup_now_asserted = false;
	hal_dev_ctx->last_wakeup_now_deasserted_time_ms =
		nrf_wifi_osal_time_get_curr_ms();
#endif /* NRF_WIFI_RPU_RECOVERY */
	hal_dev_ctx->rpu_ps_state = RPU_PS_STATE_ASLEEP;

#ifdef NRF_WIFI_RPU_RECOVERY_PS_STATE_DEBUG
	nrf_wifi_osal_log_info("%s: RPU PS state is ASLEEP",
			       __func__);
#endif /* NRF_WIFI_RPU_RECOVERY_PS_STATE_DEBUG */
	nrf_wifi_osal_spinlock_irq_rel(hal_dev_ctx->rpu_ps_lock,
				       &flags);
}


enum nrf_wifi_status hal_rpu_ps_init(struct nrf_wifi_hal_dev_ctx *hal_dev_ctx)
{
	enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL;

	hal_dev_ctx->rpu_ps_lock = nrf_wifi_osal_spinlock_alloc();

	if (!hal_dev_ctx->rpu_ps_lock) {
		nrf_wifi_osal_log_err("%s: Unable to allocate lock",
				      __func__);
		goto out;
	}

	nrf_wifi_osal_spinlock_init(hal_dev_ctx->rpu_ps_lock);

	hal_dev_ctx->rpu_ps_timer = nrf_wifi_osal_timer_alloc();

	if (!hal_dev_ctx->rpu_ps_timer) {
		nrf_wifi_osal_log_err("%s: Unable to allocate timer",
				      __func__);
		nrf_wifi_osal_spinlock_free(hal_dev_ctx->rpu_ps_lock);
		goto out;
	}

	nrf_wifi_osal_timer_init(hal_dev_ctx->rpu_ps_timer,
				 hal_rpu_ps_sleep,
				 (unsigned long)hal_dev_ctx);

	hal_dev_ctx->rpu_ps_state = RPU_PS_STATE_ASLEEP;
	hal_dev_ctx->dbg_enable = true;

	status = NRF_WIFI_STATUS_SUCCESS;
out:
	return status;
}


static void hal_rpu_ps_deinit(struct nrf_wifi_hal_dev_ctx *hal_dev_ctx)
{
	nrf_wifi_osal_timer_kill(hal_dev_ctx->rpu_ps_timer);

	nrf_wifi_osal_timer_free(hal_dev_ctx->rpu_ps_timer);

	nrf_wifi_osal_spinlock_free(hal_dev_ctx->rpu_ps_lock);
}

enum nrf_wifi_status nrf_wifi_hal_get_rpu_ps_state(
				struct nrf_wifi_hal_dev_ctx *hal_dev_ctx,
				int *rpu_ps_ctrl_state)
{
	enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL;

	if (!hal_dev_ctx) {
		nrf_wifi_osal_log_err("%s: Invalid parameters",
				      __func__);
		goto out;
	}

	*rpu_ps_ctrl_state = hal_dev_ctx->rpu_ps_state;

	return NRF_WIFI_STATUS_SUCCESS;
out:
	return status;
}
#endif /* NRF_WIFI_LOW_POWER */


static bool hal_rpu_hpq_is_empty(struct nrf_wifi_hal_dev_ctx *hal_dev_ctx,
				 struct host_rpu_hpq *hpq)
{
	enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL;
	unsigned int val = 0;

	status = hal_rpu_reg_read(hal_dev_ctx,
				  &val,
				  hpq->dequeue_addr);

	if (status != NRF_WIFI_STATUS_SUCCESS) {
		nrf_wifi_osal_log_err("%s: Read from dequeue address failed, val (0x%X)",
				      __func__,
				      val);
		return true;
	}

	if (val) {
		return false;
	}

	return true;
}


static enum nrf_wifi_status hal_rpu_ready(struct nrf_wifi_hal_dev_ctx *hal_dev_ctx,
					  enum NRF_WIFI_HAL_MSG_TYPE msg_type)
{
	bool is_empty = false;
	struct host_rpu_hpq *avl_buf_q = NULL;

	if (msg_type == NRF_WIFI_HAL_MSG_TYPE_CMD_CTRL) {
		avl_buf_q = &hal_dev_ctx->rpu_info.hpqm_info.cmd_avl_queue;
	} else {
		nrf_wifi_osal_log_err("%s: Invalid msg type %d",
				      __func__,
				      msg_type);

		return NRF_WIFI_STATUS_FAIL;
	}

	/* Check if any command pointers are available to post a message */
	is_empty = hal_rpu_hpq_is_empty(hal_dev_ctx,
					avl_buf_q);

	if (is_empty == true) {
		return NRF_WIFI_STATUS_FAIL;
	}

	return NRF_WIFI_STATUS_SUCCESS;
}


static enum nrf_wifi_status hal_rpu_ready_wait(struct nrf_wifi_hal_dev_ctx *hal_dev_ctx,
					       enum NRF_WIFI_HAL_MSG_TYPE msg_type)
{
	unsigned long start_time_us = 0;
	enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL;

	start_time_us = nrf_wifi_osal_time_get_curr_us();

	while (hal_rpu_ready(hal_dev_ctx, msg_type) != NRF_WIFI_STATUS_SUCCESS) {
		if (nrf_wifi_osal_time_elapsed_us(start_time_us) >= MAX_HAL_RPU_READY_WAIT) {
			nrf_wifi_osal_log_err("%s: Timed out waiting (msg_type = %d)",
					      __func__,
					      msg_type);
			goto out;
		}
	}

	status = NRF_WIFI_STATUS_SUCCESS;
out:
	return status;
}


static enum nrf_wifi_status hal_rpu_msg_trigger(struct nrf_wifi_hal_dev_ctx *hal_dev_ctx)
{
	enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL;

	status = hal_rpu_reg_write(hal_dev_ctx,
				   RPU_REG_INT_TO_MCU_CTRL,
				   (hal_dev_ctx->num_cmds | 0x7fff0000));

	if (status != NRF_WIFI_STATUS_SUCCESS) {
		nrf_wifi_osal_log_err("%s: Writing to MCU cmd register failed",
				      __func__);
		goto out;
	}

	hal_dev_ctx->num_cmds++;
out:
	return status;
}


enum nrf_wifi_status hal_rpu_msg_post(struct nrf_wifi_hal_dev_ctx *hal_dev_ctx,
				      enum NRF_WIFI_HAL_MSG_TYPE msg_type,
				      unsigned int queue_id,
				      unsigned int msg_addr)
{
	enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL;
	struct host_rpu_hpq *busy_queue = NULL;

	if (queue_id >= MAX_NUM_OF_RX_QUEUES) {
		nrf_wifi_osal_log_err("%s: Invalid queue_id (%d)",
				      __func__,
				      queue_id);
		goto out;
	}

	if ((msg_type == NRF_WIFI_HAL_MSG_TYPE_CMD_CTRL) ||
	    (msg_type == NRF_WIFI_HAL_MSG_TYPE_CMD_DATA_TX)) {
		busy_queue = &hal_dev_ctx->rpu_info.hpqm_info.cmd_busy_queue;
	} else if (msg_type == NRF_WIFI_HAL_MSG_TYPE_CMD_DATA_RX) {
		busy_queue = &hal_dev_ctx->rpu_info.hpqm_info.rx_buf_busy_queue[queue_id];
	} else {
		nrf_wifi_osal_log_err("%s: Invalid msg_type (%d)",
				      __func__,
				      msg_type);
		goto out;
	}

	/* Copy the address, to which information was posted,
	 * to the busy queue.
	 */
	status = hal_rpu_hpq_enqueue(hal_dev_ctx,
				     busy_queue,
				     msg_addr);

	if (status != NRF_WIFI_STATUS_SUCCESS) {
		nrf_wifi_osal_log_err("%s: Queueing of message to RPU failed",
				      __func__);
		goto out;
	}

	if (msg_type != NRF_WIFI_HAL_MSG_TYPE_CMD_DATA_RX) {
		/* Indicate to the RPU that the information has been posted */
		status = hal_rpu_msg_trigger(hal_dev_ctx);

		if (status != NRF_WIFI_STATUS_SUCCESS) {
			nrf_wifi_osal_log_err("%s: Posting command to RPU failed",
					      __func__);
			goto out;
		}
	}
out:
	return status;
}


static enum nrf_wifi_status hal_rpu_msg_get_addr(struct nrf_wifi_hal_dev_ctx *hal_dev_ctx,
						 enum NRF_WIFI_HAL_MSG_TYPE msg_type,
						 unsigned int *msg_addr)
{
	enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL;
	struct host_rpu_hpq *avl_queue = NULL;

	if (msg_type == NRF_WIFI_HAL_MSG_TYPE_CMD_CTRL) {
		avl_queue = &hal_dev_ctx->rpu_info.hpqm_info.cmd_avl_queue;
	} else {
		nrf_wifi_osal_log_err("%s: Invalid msg_type (%d)",
				      __func__,
				      msg_type);
		goto out;
	}

	status = hal_rpu_hpq_dequeue(hal_dev_ctx,
				     avl_queue,
				     msg_addr);

	if (status != NRF_WIFI_STATUS_SUCCESS) {
		nrf_wifi_osal_log_err("%s: Dequeue of address failed msg_addr 0x%X",
				      __func__,
				      *msg_addr);
		*msg_addr = 0;
		goto out;
	}
out:
	return status;
}
#endif /* !NRF71_ON_IPC */

static enum nrf_wifi_status hal_rpu_msg_write(struct nrf_wifi_hal_dev_ctx *hal_dev_ctx,
					      enum NRF_WIFI_HAL_MSG_TYPE msg_type,
					      void *msg,
					      unsigned int len)
{
	enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL;
#ifndef NRF71_ON_IPC
	unsigned int msg_addr = 0;

	/* Get the address from the RPU to which
	 * the command needs to be copied to
	 */
	status = hal_rpu_msg_get_addr(hal_dev_ctx,
				      msg_type,
				      &msg_addr);

	if (status != NRF_WIFI_STATUS_SUCCESS) {
		nrf_wifi_osal_log_err("%s: Getting address (0x%X) to post message failed",
				      __func__,
				      msg_addr);
		goto out;
	}

	/* Copy the information to the suggested address */
	status = hal_rpu_mem_write(hal_dev_ctx,
				   msg_addr,
				   msg,
				   len);

	if (status != NRF_WIFI_STATUS_SUCCESS) {
		nrf_wifi_osal_log_err("%s: Copying information to RPU failed",
				      __func__);
		goto out;
	}

	/* Post the updated information to the RPU */
	status = hal_rpu_msg_post(hal_dev_ctx,
				  msg_type,
				  0,
				  msg_addr);

	if (status != NRF_WIFI_STATUS_SUCCESS) {
		nrf_wifi_osal_log_err("%s: Posting command to RPU failed",
				      __func__);
		goto out;
	}
#else
	status = nrf_wifi_osal_ipc_send_msg(msg_type, msg, len);
	if (status != NRF_WIFI_STATUS_SUCCESS) {
		nrf_wifi_osal_log_err("%s: Sending message to RPU failed",
				      __func__);
		goto out;
	}
#endif /* !NRF71_ON_IPC */
out:
	return status;
}


static enum nrf_wifi_status hal_rpu_cmd_process_queue(struct nrf_wifi_hal_dev_ctx *hal_dev_ctx)
{
	enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL;
	struct nrf_wifi_hal_msg *cmd = NULL;

	while ((cmd = nrf_wifi_utils_ctrl_q_dequeue(hal_dev_ctx->cmd_q))) {
#ifndef NRF71_ON_IPC
		status = hal_rpu_ready_wait(hal_dev_ctx,
					    NRF_WIFI_HAL_MSG_TYPE_CMD_CTRL);

		if (status != NRF_WIFI_STATUS_SUCCESS) {
			nrf_wifi_osal_log_err("%s: Timeout waiting to get free cmd buff from RPU",
					      __func__);
			nrf_wifi_osal_mem_free(cmd);
			cmd = NULL;
			continue;
		}
#endif /* !NRF71_ON_IPC */
		status = hal_rpu_msg_write(hal_dev_ctx,
					   NRF_WIFI_HAL_MSG_TYPE_CMD_CTRL,
					   cmd->data,
					   cmd->len);

		if (status != NRF_WIFI_STATUS_SUCCESS) {
			nrf_wifi_osal_log_err("%s: Writing command to RPU failed",
					      __func__);
			nrf_wifi_osal_mem_free(cmd);
			cmd = NULL;
			continue;
		}

		/* Free the command data and command */
		nrf_wifi_osal_mem_free(cmd);
		cmd = NULL;
	}

	return status;
}


static enum nrf_wifi_status hal_rpu_cmd_queue(struct nrf_wifi_hal_dev_ctx *hal_dev_ctx,
					      void *cmd,
					      unsigned int cmd_size)
{
	int len = 0;
	int size = 0;
	char *data = NULL;
	struct nrf_wifi_hal_msg *hal_msg = NULL;
	enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL;

	len = cmd_size;
	data = cmd;

	if (len > hal_dev_ctx->hpriv->cfg_params.max_cmd_size) {
		while (len > 0) {
			if (len > hal_dev_ctx->hpriv->cfg_params.max_cmd_size) {
				size = hal_dev_ctx->hpriv->cfg_params.max_cmd_size;
			} else {
				size = len;
			}

			hal_msg = nrf_wifi_osal_mem_zalloc(sizeof(*hal_msg) + size);

			if (!hal_msg) {
				nrf_wifi_osal_log_err("%s: Unable to alloc buff for frag HAL cmd",
						      __func__);
				status = NRF_WIFI_STATUS_FAIL;
				goto out;
			}

			nrf_wifi_osal_mem_cpy(hal_msg->data,
					      data,
					      size);

			hal_msg->len = size;

			status = nrf_wifi_utils_ctrl_q_enqueue(hal_dev_ctx->cmd_q,
							  hal_msg);

			if (status != NRF_WIFI_STATUS_SUCCESS) {
				nrf_wifi_osal_log_err("%s: Unable to queue frag HAL cmd",
						      __func__);
				goto out;
			}

			len -= size;
			data += size;
		}
	} else {
		hal_msg = nrf_wifi_osal_mem_zalloc(sizeof(*hal_msg) + len);

		if (!hal_msg) {
			nrf_wifi_osal_log_err("%s: Unable to allocate buffer for HAL command",
					      __func__);
			status = NRF_WIFI_STATUS_FAIL;
			goto out;
		}

		nrf_wifi_osal_mem_cpy(hal_msg->data,
				      cmd,
				      len);

		hal_msg->len = len;

		status = nrf_wifi_utils_ctrl_q_enqueue(hal_dev_ctx->cmd_q,
						  hal_msg);

		if (status != NRF_WIFI_STATUS_SUCCESS) {
			nrf_wifi_osal_log_err("%s: Unable to queue fragmented command",
					      __func__);
			goto out;
		}
	}

	/* Free the original command data */
	nrf_wifi_osal_mem_free(cmd);

out:
	return status;
}


enum nrf_wifi_status nrf_wifi_hal_ctrl_cmd_send(struct nrf_wifi_hal_dev_ctx *hal_dev_ctx,
						void *cmd,
						unsigned int cmd_size)
{
	enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL;


#ifdef CONFIG_NRF_WIFI_CMD_EVENT_LOG
	nrf_wifi_osal_log_info("%s: caller %p",
			      __func__,
			      __builtin_return_address(0));
#else
	nrf_wifi_osal_log_dbg("%s: caller %p",
			     __func__,
			     __builtin_return_address(0));
#endif
	nrf_wifi_osal_spinlock_take(hal_dev_ctx->lock_hal);

	status = hal_rpu_cmd_queue(hal_dev_ctx,
				   cmd,
				   cmd_size);

	if (status != NRF_WIFI_STATUS_SUCCESS) {
		nrf_wifi_osal_log_err("%s: Queueing of command failed",
				      __func__);
		goto out;
	}

	status = hal_rpu_cmd_process_queue(hal_dev_ctx);

out:
	nrf_wifi_osal_spinlock_rel(hal_dev_ctx->lock_hal);

	return status;
}


enum nrf_wifi_status hal_rpu_eventq_process(struct nrf_wifi_hal_dev_ctx *hal_dev_ctx)
{
	enum nrf_wifi_status status = NRF_WIFI_STATUS_SUCCESS;
	struct nrf_wifi_hal_msg *event = NULL;
	void *event_data = NULL;
	unsigned int event_len = 0;

	while (1) {
		event = nrf_wifi_utils_ctrl_q_dequeue(hal_dev_ctx->event_q);
		if (!event) {
			goto out;
		}

		event_data = event->data;
		event_len = event->len;

		/* Process the event further */
		status = hal_dev_ctx->hpriv->intr_callbk_fn(hal_dev_ctx->mac_dev_ctx,
							    event_data,
							    event_len);

		if (status != NRF_WIFI_STATUS_SUCCESS) {
			nrf_wifi_osal_log_err("%s: Interrupt callback failed",
					      __func__);
		}

		/* Free up the local buffer */
		nrf_wifi_osal_mem_free(event);
		event = NULL;
	}

out:
	return status;
}

static void hal_rpu_eventq_drain(struct nrf_wifi_hal_dev_ctx *hal_dev_ctx)
{
	struct nrf_wifi_hal_msg *event = NULL;
	unsigned long flags = 0;

	while (1) {
		nrf_wifi_osal_spinlock_irq_take(hal_dev_ctx->lock_rx,
						&flags);

		event = nrf_wifi_utils_ctrl_q_dequeue(hal_dev_ctx->event_q);

		nrf_wifi_osal_spinlock_irq_rel(hal_dev_ctx->lock_rx,
					       &flags);

		if (!event) {
			goto out;
		}

		/* Free up the local buffer */
		nrf_wifi_osal_mem_free(event);
		event = NULL;
	}

out:
	return;
}

void nrf_wifi_hal_proc_ctx_set(struct nrf_wifi_hal_dev_ctx *hal_dev_ctx,
			       enum RPU_PROC_TYPE proc)
{
	hal_dev_ctx->curr_proc = proc;
}


void nrf_wifi_hal_dev_rem(struct nrf_wifi_hal_dev_ctx *hal_dev_ctx)
{
	unsigned int i = 0;

	nrf_wifi_osal_tasklet_kill(hal_dev_ctx->recovery_tasklet);
	nrf_wifi_osal_tasklet_free(hal_dev_ctx->recovery_tasklet);
	nrf_wifi_osal_spinlock_free(hal_dev_ctx->lock_recovery);

	nrf_wifi_osal_tasklet_kill(hal_dev_ctx->event_tasklet);

	nrf_wifi_osal_tasklet_free(hal_dev_ctx->event_tasklet);

	hal_rpu_eventq_drain(hal_dev_ctx);

	nrf_wifi_osal_spinlock_free(hal_dev_ctx->lock_hal);
	nrf_wifi_osal_spinlock_free(hal_dev_ctx->lock_rx);

	nrf_wifi_utils_ctrl_q_free(hal_dev_ctx->event_q);

	nrf_wifi_utils_ctrl_q_free(hal_dev_ctx->cmd_q);

#ifdef NRF_WIFI_LOW_POWER
	hal_rpu_ps_deinit(hal_dev_ctx);
#endif /* NRF_WIFI_LOW_POWER */

	nrf_wifi_bal_dev_rem(hal_dev_ctx->bal_dev_ctx);

	nrf_wifi_osal_mem_free(hal_dev_ctx->tx_buf_info);
	hal_dev_ctx->tx_buf_info = NULL;

	for (i = 0; i < MAX_NUM_OF_RX_QUEUES; i++) {
		nrf_wifi_osal_mem_free(hal_dev_ctx->rx_buf_info[i]);
		hal_dev_ctx->rx_buf_info[i] = NULL;
	}

	hal_dev_ctx->hpriv->num_devs--;

	nrf_wifi_osal_mem_free(hal_dev_ctx);
}


enum nrf_wifi_status nrf_wifi_hal_dev_init(struct nrf_wifi_hal_dev_ctx *hal_dev_ctx)
{
	enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL;

#ifdef NRF_WIFI_LOW_POWER
	hal_dev_ctx->rpu_fw_booted = true;
#endif /* NRF_WIFI_LOW_POWER */

	status = nrf_wifi_bal_dev_init(hal_dev_ctx->bal_dev_ctx);

	if (status != NRF_WIFI_STATUS_SUCCESS) {
		nrf_wifi_osal_log_err("%s: nrf_wifi_bal_dev_init failed",
				      __func__);
		goto out;
	}

#ifndef NRF71_ON_IPC
	/* Read the HPQM info for all the queues provided by the RPU
	 * (like command, event, RX buf queues etc)
	 */
	status = hal_rpu_mem_read(hal_dev_ctx,
				  &hal_dev_ctx->rpu_info.hpqm_info,
				  RPU_MEM_HPQ_INFO,
				  sizeof(hal_dev_ctx->rpu_info.hpqm_info));

	if (status != NRF_WIFI_STATUS_SUCCESS) {
		nrf_wifi_osal_log_err("%s: Failed to get the HPQ info",
				      __func__);
		goto out;
	}

	status = hal_rpu_mem_read(hal_dev_ctx,
				  &hal_dev_ctx->rpu_info.rx_cmd_base,
				  RPU_MEM_RX_CMD_BASE,
				  sizeof(hal_dev_ctx->rpu_info.rx_cmd_base));

	if (status != NRF_WIFI_STATUS_SUCCESS) {
		nrf_wifi_osal_log_err("%s: Reading the RX cmd base failed",
				      __func__);
		goto out;
	}

	hal_dev_ctx->rpu_info.tx_cmd_base = RPU_MEM_TX_CMD_BASE;
#endif /* !NRF71_ON_IPC */
	nrf_wifi_hal_enable(hal_dev_ctx);
out:
	return status;
}

#ifdef NRF71_ON_IPC
enum nrf_wifi_status nrf_wifi_hal_ipc_msg_handler(void *priv)
{
	enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL;
	struct nrf_wifi_hal_dev_ctx *hal_dev_ctx = (struct nrf_wifi_hal_dev_ctx *) priv;
	void *event_data = hal_dev_ctx->ipc_msg;
	/* IPC message is a pointer to PKTRAM address so the len is not relevant */
	unsigned int event_len = sizeof(event_data);

	nrf_wifi_osal_log_dbg("%s: IPC message received\n", __func__);
	status = hal_dev_ctx->hpriv->intr_callbk_fn(hal_dev_ctx->mac_dev_ctx, event_data, event_len);

	return status;
}
#endif /* NRF71_ON_IPC */

void nrf_wifi_hal_dev_deinit(struct nrf_wifi_hal_dev_ctx *hal_dev_ctx)
{
	nrf_wifi_hal_disable(hal_dev_ctx);
	nrf_wifi_bal_dev_deinit(hal_dev_ctx->bal_dev_ctx);
	hal_rpu_eventq_drain(hal_dev_ctx);
}


#ifndef NRF71_ON_IPC
enum nrf_wifi_status nrf_wifi_hal_irq_handler(void *data)
{
	struct nrf_wifi_hal_dev_ctx *hal_dev_ctx = NULL;
	enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL;
	unsigned long flags = 0;
	bool do_rpu_recovery = false;

	hal_dev_ctx = (struct nrf_wifi_hal_dev_ctx *)data;

	nrf_wifi_osal_spinlock_irq_take(hal_dev_ctx->lock_rx,
					&flags);

	if (hal_dev_ctx->hal_status != NRF_WIFI_HAL_STATUS_ENABLED) {
		/* Ignore the interrupt if the HAL is not enabled */
		status = NRF_WIFI_STATUS_SUCCESS;
		goto out;
	}


	status = hal_rpu_irq_process(hal_dev_ctx, &do_rpu_recovery);

	if (status != NRF_WIFI_STATUS_SUCCESS) {
		goto out;
	}

	if (do_rpu_recovery) {
		nrf_wifi_osal_tasklet_schedule(hal_dev_ctx->recovery_tasklet);
		goto out;
	}

	nrf_wifi_osal_tasklet_schedule(hal_dev_ctx->event_tasklet);

out:
	nrf_wifi_osal_spinlock_irq_rel(hal_dev_ctx->lock_rx,
				       &flags);
	return status;
}
#endif /* !NRF71_ON_IPC */

static int nrf_wifi_hal_poll_reg(struct nrf_wifi_hal_dev_ctx *hal_dev_ctx,
				 unsigned int reg_addr,
				 unsigned int mask,
				 unsigned int req_value,
				 unsigned int poll_delay)
{
	enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL;
	unsigned int val = 0;
	unsigned int count = 50;

	do {
		status = hal_rpu_reg_read(hal_dev_ctx,
					  &val,
					  reg_addr);

		if (status != NRF_WIFI_STATUS_SUCCESS) {
			nrf_wifi_osal_log_err("%s: Read from address (0x%X) failed, val (0x%X)",
					      __func__,
					      reg_addr,
					      val);
		}

		if ((val & mask) == req_value) {
			status = NRF_WIFI_STATUS_SUCCESS;
			break;
		}

		nrf_wifi_osal_sleep_ms(poll_delay);
	} while (count-- > 0);

	if (count == 0) {
		nrf_wifi_osal_log_err("%s: Timed out polling on (0x%X)",
				      __func__,
				      reg_addr);

		status = NRF_WIFI_STATUS_FAIL;
		goto out;
	}
out:
	return status;
}


/* Perform MIPS reset */
enum nrf_wifi_status nrf_wifi_hal_proc_reset(struct nrf_wifi_hal_dev_ctx *hal_dev_ctx,
					     enum RPU_PROC_TYPE rpu_proc)
{
	enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL;

	if ((rpu_proc != RPU_PROC_TYPE_MCU_LMAC) &&
	    (rpu_proc != RPU_PROC_TYPE_MCU_UMAC)) {
		nrf_wifi_osal_log_err("%s: Unsupported RPU processor(%d)",
				      __func__,
				      rpu_proc);
		goto out;
	}

	hal_dev_ctx->curr_proc = rpu_proc;

	/* Perform pulsed soft reset of MIPS */
	if (rpu_proc == RPU_PROC_TYPE_MCU_LMAC) {
		status = hal_rpu_reg_write(hal_dev_ctx,
					   RPU_REG_MIPS_MCU_CONTROL,
					   0x1);
	} else {
		status = hal_rpu_reg_write(hal_dev_ctx,
					   RPU_REG_MIPS_MCU2_CONTROL,
					   0x1);
	}

	if (status != NRF_WIFI_STATUS_SUCCESS) {
		nrf_wifi_osal_log_err("%s: Pulsed soft reset of MCU failed for (%d) processor",
				      __func__,
				      rpu_proc);
		goto out;
	}


	/* Wait for it to come out of reset */
	if (rpu_proc == RPU_PROC_TYPE_MCU_LMAC) {
		status = nrf_wifi_hal_poll_reg(hal_dev_ctx,
					       RPU_REG_MIPS_MCU_CONTROL,
					       0x1,
					       0,
					       10);
	} else {
		status = nrf_wifi_hal_poll_reg(hal_dev_ctx,
					       RPU_REG_MIPS_MCU2_CONTROL,
					       0x1,
					       0,
					       10);
	}

	if (status != NRF_WIFI_STATUS_SUCCESS) {
		nrf_wifi_osal_log_err("%s: MCU (%d) failed to come out of reset",
				      __func__,
				      rpu_proc);
		goto out;
	}

	/* MIPS will restart from it's boot exception registers
	 * and hit its default wait instruction
	 */
	if (rpu_proc == RPU_PROC_TYPE_MCU_LMAC) {
		status = nrf_wifi_hal_poll_reg(hal_dev_ctx,
					       0xA4000018,
					       0x1,
					       0x1,
					       10);
	} else {
		status = nrf_wifi_hal_poll_reg(hal_dev_ctx,
					       0xA4000118,
					       0x1,
					       0x1,
					       10);
	}
out:
	hal_dev_ctx->curr_proc = RPU_PROC_TYPE_MCU_LMAC;
	return status;
}

#ifndef NRF71_ON_IPC
#define MCU_FW_BOOT_TIMEOUT_MS 1000
enum nrf_wifi_status nrf_wifi_hal_fw_chk_boot(struct nrf_wifi_hal_dev_ctx *hal_dev_ctx,
					      enum RPU_PROC_TYPE rpu_proc)
{
	enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL;
	unsigned int addr = 0;
	unsigned int val = 0;
	unsigned int exp_val = 0;
	int mcu_ready_wait_count = MCU_FW_BOOT_TIMEOUT_MS / 10;

	if (rpu_proc == RPU_PROC_TYPE_MCU_LMAC) {
		addr = RPU_MEM_LMAC_BOOT_SIG;
		exp_val = NRF_WIFI_LMAC_BOOT_SIG;
	} else if (rpu_proc == RPU_PROC_TYPE_MCU_UMAC) {
		addr = RPU_MEM_UMAC_BOOT_SIG;
		exp_val = NRF_WIFI_UMAC_BOOT_SIG;
	} else {
		nrf_wifi_osal_log_err("%s: Invalid RPU processor (%d)",
				      __func__,
				      rpu_proc);
	}

	hal_dev_ctx->curr_proc = rpu_proc;

	while (mcu_ready_wait_count-- > 0) {
		status = hal_rpu_mem_read(hal_dev_ctx,
					  (unsigned char *)&val,
					  addr,
					  sizeof(val));

		if (status != NRF_WIFI_STATUS_SUCCESS) {
			nrf_wifi_osal_log_err("%s: Reading of boot signature failed for RPU(%d)",
					      __func__,
					      rpu_proc);
		}

		if (val == exp_val) {
			break;
		}

		/* Sleep for 10 ms */
		nrf_wifi_osal_sleep_ms(10);
	};

	if (mcu_ready_wait_count <= 0) {
		nrf_wifi_osal_log_err("%s: Boot_sig check failed for RPU(%d), "
				      "Expected: 0x%X, Actual: 0x%X",
				      __func__,
				      rpu_proc,
				      exp_val,
				      val);
		status = NRF_WIFI_STATUS_FAIL;
		goto out;
	}

	status = NRF_WIFI_STATUS_SUCCESS;
out:
	hal_dev_ctx->curr_proc = RPU_PROC_TYPE_MCU_LMAC;

	return status;
}
#endif /* !NRF71_ON_IPC */

struct nrf_wifi_hal_priv *
nrf_wifi_hal_init(struct nrf_wifi_hal_cfg_params *cfg_params,
		  enum nrf_wifi_status (*intr_callbk_fn)(void *dev_ctx,
							 void *event_data,
							 unsigned int len),
		  enum nrf_wifi_status (*rpu_recovery_callbk_fn)(void *mac_ctx,
								 void *event_data,
								 unsigned int len))
{
	enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL;
	struct nrf_wifi_hal_priv *hpriv = NULL;
	struct nrf_wifi_bal_cfg_params bal_cfg_params;

	hpriv = nrf_wifi_osal_mem_zalloc(sizeof(*hpriv));

	if (!hpriv) {
		nrf_wifi_osal_log_err("%s: Unable to allocate memory for hpriv",
				      __func__);
		goto out;
	}

	nrf_wifi_osal_mem_cpy(&hpriv->cfg_params,
			      cfg_params,
			      sizeof(hpriv->cfg_params));

	hpriv->intr_callbk_fn = intr_callbk_fn;
	hpriv->rpu_recovery_callbk_fn = rpu_recovery_callbk_fn;

#ifndef NRF71_ON_IPC
	status = pal_rpu_addr_offset_get(RPU_ADDR_PKTRAM_START,
					 &hpriv->addr_pktram_base,
					 RPU_PROC_TYPE_MAX);

	if (status != NRF_WIFI_STATUS_SUCCESS) {
		nrf_wifi_osal_log_err("%s: pal_rpu_addr_offset_get failed",
				      __func__);
		goto out;
	}

	bal_cfg_params.addr_pktram_base = hpriv->addr_pktram_base;

	hpriv->bpriv = nrf_wifi_bal_init(&bal_cfg_params,
					 &nrf_wifi_hal_irq_handler);

#else /* !NRF71_ON_IPC */
	ARG_UNUSED(status);
	/* PKTRAM base addr is not needed for IPC */
	hpriv->bpriv = nrf_wifi_bal_init(&bal_cfg_params, &nrf_wifi_hal_ipc_msg_handler);
#endif /* !NRF71_ON_IPC */

	if (!hpriv->bpriv) {
		nrf_wifi_osal_log_err("%s: Failed",
				      __func__);
		nrf_wifi_osal_mem_free(hpriv);
		hpriv = NULL;
	}
out:
	return hpriv;
}


void nrf_wifi_hal_deinit(struct nrf_wifi_hal_priv *hpriv)
{
	nrf_wifi_bal_deinit(hpriv->bpriv);

	nrf_wifi_osal_mem_free(hpriv);
}

#ifndef NRF71_ON_IPC
enum nrf_wifi_status nrf_wifi_hal_otp_info_get(struct nrf_wifi_hal_dev_ctx *hal_dev_ctx,
					       struct host_rpu_umac_info *otp_info,
					       unsigned int *otp_flags)
{
	enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL;

	if (!hal_dev_ctx || !otp_info) {
		nrf_wifi_osal_log_err("%s: Invalid parameters",
				      __func__);
		goto out;
	}

	status = hal_rpu_mem_read(hal_dev_ctx,
				  otp_info,
				  RPU_MEM_UMAC_BOOT_SIG,
				  sizeof(*otp_info));

	if (status != NRF_WIFI_STATUS_SUCCESS) {
		nrf_wifi_osal_log_err("%s: OTP info get failed",
				      __func__);
		goto out;
	}

	status = hal_rpu_mem_read(hal_dev_ctx,
				  otp_flags,
				  RPU_MEM_OTP_INFO_FLAGS,
				  sizeof(*otp_flags));

	if (status != NRF_WIFI_STATUS_SUCCESS) {
		nrf_wifi_osal_log_err("%s: OTP flags get failed",
				      __func__);
		goto out;
	}
out:
	return status;
}


enum nrf_wifi_status nrf_wifi_hal_otp_ft_prog_ver_get(struct nrf_wifi_hal_dev_ctx *hal_dev_ctx,
						      unsigned int *ft_prog_ver)
{
	enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL;

	if (!hal_dev_ctx || !ft_prog_ver) {
		nrf_wifi_osal_log_err("%s: Invalid parameters",
				      __func__);
		goto out;
	}

	status = hal_rpu_mem_read(hal_dev_ctx,
				  ft_prog_ver,
				  RPU_MEM_OTP_FT_PROG_VERSION,
				  sizeof(*ft_prog_ver));

	if (status != NRF_WIFI_STATUS_SUCCESS) {
		nrf_wifi_osal_log_err("%s: FT program version get failed",
				      __func__);
		goto out;
	}
out:
	return status;
}

enum nrf_wifi_status nrf_wifi_hal_otp_pack_info_get(struct nrf_wifi_hal_dev_ctx *hal_dev_ctx,
						    unsigned int *package_info)
{
	enum nrf_wifi_status status = NRF_WIFI_STATUS_FAIL;

	if (!hal_dev_ctx || !package_info) {
		nrf_wifi_osal_log_err("%s: Invalid parameters",
				      __func__);
		goto out;
	}

	status = hal_rpu_mem_read(hal_dev_ctx,
				  package_info,
				  RPU_MEM_OTP_PACKAGE_TYPE,
				  sizeof(*package_info));

	if (status != NRF_WIFI_STATUS_SUCCESS) {
		nrf_wifi_osal_log_err("%s: Package info get failed",
				      __func__);
		goto out;
	}
out:
	return status;
}
#endif /* !NRF71_ON_IPC */

void nrf_wifi_hal_enable(struct nrf_wifi_hal_dev_ctx *hal_dev_ctx)
{
	nrf_wifi_osal_spinlock_irq_take(hal_dev_ctx->lock_rx,
					NULL);
	hal_dev_ctx->hal_status = NRF_WIFI_HAL_STATUS_ENABLED;
	nrf_wifi_osal_spinlock_irq_rel(hal_dev_ctx->lock_rx,
				       NULL);
}

void nrf_wifi_hal_disable(struct nrf_wifi_hal_dev_ctx *hal_dev_ctx)
{
	nrf_wifi_osal_spinlock_irq_take(hal_dev_ctx->lock_rx,
					NULL);
	hal_dev_ctx->hal_status = NRF_WIFI_HAL_STATUS_DISABLED;
	nrf_wifi_osal_spinlock_irq_rel(hal_dev_ctx->lock_rx,
				       NULL);
}

enum NRF_WIFI_HAL_STATUS nrf_wifi_hal_status_unlocked(struct nrf_wifi_hal_dev_ctx *hal_dev_ctx)
{
	return hal_dev_ctx->hal_status;
}
